﻿using System.Runtime.InteropServices;

namespace Aeon.Emulator
{
    /// <summary>
    /// Represents an 80-bit extended-precision floating-point number.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Real10
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the Real10 struct.
        /// </summary>
        /// <param name="mantissa">Mantissa binary value.</param>
        /// <param name="exponentAndSign">Exponent binary value with sign bit.</param>
        private Real10(ulong mantissa, ushort exponentAndSign)
        {
            unsafe
            {
                fixed(byte* dataPtr = this.data)
                {
                    *(ulong*)dataPtr = mantissa | 0x8000000000000000u;
                    *(ushort*)&dataPtr[8] = exponentAndSign;
                }
            }
        }
        #endregion

        #region Operators
        /// <summary>
        /// Converts a Real10 into a System.Double.
        /// </summary>
        /// <param name="value">Real10 value to convert.</param>
        /// <returns>System.Double converted from the Real10.</returns>
        public static explicit operator double(Real10 value)
        {
            return value.ToDouble();
        }
        /// <summary>
        /// Converts a System.Double into a Real10.
        /// </summary>
        /// <param name="value">System.Double value to convert.</param>
        /// <returns>Real10 converted from the System.Double.</returns>
        public static implicit operator Real10(double value)
        {
            return FromDouble(value);
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Returns a string representation of the Real10 value.
        /// </summary>
        /// <returns>String representation of the Real10 value.</returns>
        public override string ToString()
        {
            return ((double)this).ToString();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Returns a System.Double value converted from this Real10 instance.
        /// </summary>
        /// <returns>System.Double value converted from this Real10 instance.</returns>
        private double ToDouble()
        {
            unsafe
            {
                fixed(byte* dataPtr = this.data)
                {
                    byte* doublePtr = stackalloc byte[8];

                    // The mantissa is the low 63 bits.
                    ulong mantissa = *(ulong*)dataPtr & 0x7FFFFFFFFFFFFFFFu;

                    // The exponent is the next 15 bits.
                    int exponent = *(ushort*)&dataPtr[8] & 0x7FFF;

                    // The sign is the highest bit.
                    byte sign = (byte)(dataPtr[9] & 0x80u);

                    // Drop the lowest 11 bits from the mantissa.
                    mantissa >>= 11;

                    if(exponent == 0)
                        return 0.0;

                    if(exponent == 0x7FFF) //+infinity, -infinity or nan
                    {
                        if(mantissa != 0)
                            return double.NaN;
                        if(sign == 0)
                            return double.PositiveInfinity;
                        else
                            return double.NegativeInfinity;
                    }

                    exponent -= (0x3FFF - 0x3FF);

                    if(exponent >= 0x7FF)
                        return sign == 0 ? double.PositiveInfinity : double.NegativeInfinity;
                    else if(exponent < -51)
                        return 0.0;
                    else if(exponent < 0)
                    {
                        mantissa |= 0x1000000000000000u;
                        mantissa >>= 1 - exponent;
                        exponent = 0;
                    }

                    *(ulong*)doublePtr = mantissa & 0x000FFFFFFFFFFFFFu;
                    *(ulong*)doublePtr |= ((ulong)(uint)exponent) << 52;
                    doublePtr[7] |= sign;

                    return *(double*)doublePtr;
                }
            }
        }
        #endregion

        #region Private Static Methods
        /// <summary>
        /// Returns a Real10 value converted from a System.Double.
        /// </summary>
        /// <param name="value">System.Double to convert to a Real10 value.</param>
        /// <returns>Real10 value converted from the System.Double.</returns>
        private static Real10 FromDouble(double value)
        {
            if(double.IsPositiveInfinity(value))
                return Real10.PositiveInfinity;
            else if(double.IsNegativeInfinity(value))
                return Real10.NegativeInfinity;
            else if(double.IsNaN(value))
                return Real10.NaN;

            ulong doubleInt;
            unsafe
            {
                doubleInt = *(ulong*)&value;
            }

            ulong mantissa = (doubleInt & 0x000FFFFFFFFFFFFFu) << 11;

            uint exponent = (uint)(doubleInt >> 52) & 0x7FFu;
            exponent += (0x3FFF - 0x3FF);

            if((doubleInt & 0x8000000000000000u) != 0)
                exponent |= 0x8000;

            return new Real10(mantissa, (ushort)exponent);
        }
        #endregion

        #region Public Static Fields
        /// <summary>
        /// Represents positive infinity.
        /// </summary>
        public static readonly Real10 PositiveInfinity = new Real10(0, 0x7FFF);
        /// <summary>
        /// Represents negative infinity.
        /// </summary>
        public static readonly Real10 NegativeInfinity = new Real10(0, 0xFFFF);
        /// <summary>
        /// Represents a value that is not a number (NaN).
        /// </summary>
        public static readonly Real10 NaN = new Real10(0x7FFFFFFFFFFFFFFF, 0xFFFF);
        #endregion

        #region Private Fields
        /// <summary>
        /// Stores the 10-byte Real10 value.
        /// </summary>
        private unsafe fixed byte data[10];
        #endregion
    }
}
